package com.yeqian.user.config;


import com.yeqian.user.configuration.AuthorConfigProperties;
import com.yeqian.user.handle.*;
import com.yeqian.user.security.YeqAuthenticationEntryPoint;
import com.yeqian.user.security.YeqExpiredSessionStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * SpringSecurity
 *
 * @Author : yeqian
 * @Date : 2021/5/7 21:14
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private YeqAuthenticationSuccessHandler authenticationSuccessHandler;

    @Resource
    private DataSource dataSource;

    @Resource
    private YeqLogoutSuccessHandler logoutSuccessHandler;

    @Resource
    private YeqAuthenticationFailHandler authenticationFailHandler;

    @Resource
    private YeqAuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private YeqLogoutHandler logoutHandler;

    @Resource
    private YeqAccesDeniedHandler accesDeniedHandler;

    @Resource
    private YeqExpiredSessionStrategy expiredSessionStrategy;

    @Autowired
    private AuthorConfigProperties authorConfigProperties;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().sameOrigin();
        //Springsecurity会默认开启csrf功能，需要将其关闭，否则只能使用get请求来提交
        http
                .authorizeRequests()
//                .antMatchers(HttpMethod.GET, "/getInfo").access("hasAuthority('system')")
                // 允许登录页面匿名访问,仅允许匿名用户访问,如果登录认证后，带有token信息再去请求，这个anonymous()关联的资源就不能被访问(就相当于登陆之后不允许访问,只允许匿名的用户)
//                .antMatchers("/").anonymous()
                // 登录能访问,不登录也能访问,一般用于静态资源js等
                .antMatchers(authorConfigProperties.getWhitelist()).permitAll()
                // 匹配所有的请求，并且所有请求都需要登录认证
                .anyRequest().authenticated().and()
                // 登录
                .formLogin()
                .loginProcessingUrl(authorConfigProperties.getLoginUrl())
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailHandler)
                .and()
                // 注销
                .logout().logoutUrl("/logout").addLogoutHandler(logoutHandler)
                .logoutSuccessHandler(logoutSuccessHandler)
                .deleteCookies("JSESSIONID")
                .and()
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                // 设置cookie的有效时间（单位：秒）
                .tokenValiditySeconds(10)
                .userDetailsService(userDetailsService)
                .and()
                .exceptionHandling()
                // 未登录处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                .and()
                .exceptionHandling()
                // 权限不足处理
                .accessDeniedHandler(accesDeniedHandler)
                .and()
                // csrf相关
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                /**
                 always：如果当前请求没有session存在，Spring Security创建一个session。
                 ifRequired（默认）： Spring Security在需要时才创建session
                 never： Spring Security将永远不会主动创建session，但是如果session已经存在，它将使用该session
                 stateless：Spring Security不会创建或使用任何session。适合于接口型的无状态应用，该方式节省资源。
                 */
                // 设置最大允许登陆数量为1
                .maximumSessions(1)
                // 是否允许另一个账户登录
                .maxSessionsPreventsLogin(false)
                // 被挤下线的处理方式
                .expiredSessionStrategy(expiredSessionStrategy);
    }


    /**
     * 注入PasswordEncoder对象
     *
     * @return
     */
    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 开启自动生成表
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    /**
     * 认证方法，修改HideUserNotFoundExceptions为false来返回登录错误信息
     *
     * @return
     */
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(getPasswordEncoder());
        return provider;
    }
}
